import SocketServiceImpl from "./core";
import SocketUtils from "../utils/socket-utils";
import BattleUtils from "../utils/battle-utils";
import {BATTLE, BATTLE_FLOW_TYPE, INS_KEY, LOGIN_TYPE} from "../configs/config";
import CryptorUtils from "../utils/cryptor-utils";
import CaptchaUtils from "../utils/captcha-utils";
import UserStateDao from "../dao/user-state-dao";
import BoardUtils from "../utils/board-utils";
import InsUtils from "../utils/ins-utils";

const AsyncLock = require('async-lock');
const lock = new AsyncLock();
const socketUtils = new SocketUtils();
const battleUtils = new BattleUtils();
const boardUtils = new BoardUtils();
const insUtils = new InsUtils();
const serviceImpl = new SocketServiceImpl();

// Api认证白名单(默认会对所有接口进行token认证)
const API_AUTH_WHITE_NAME_LIST = ['loginApi', 'generateTouristApi', 'registerApi', 'sendValidCodeApi',
    'forgetPasswordApi', 'forgetSendValidCodeApi', 'loginOutApi', 'versionDetailApi', 'getCaptchaApi']

class SocketListen {

    constructor(io: any) {

        // 服务启动监听
        io.use((socket: any, next: Function) => {
            const {token, f} = socket.handshake.query;
            if (token && f) return next();
            return next(new Error("凭证或指纹采集失败"));
        }).on('connection', (socket: any) => {
            global.logUtils.createTraceId(socketUtils.getToken(socket));
            const log = global.logUtils.createContext('SocketListen', 'constructor');
            log.info(`token[${socketUtils.getToken(socket)}]已经连接，ip为：[${socketUtils.getClientIp(socket)}]`);

            socket.prependAny(async (api: any, ...args: any[]) => {
                // 数据解密
                const request = args ? CryptorUtils.decrypt(args[0]) : {};
                const userId = request?.userId || '';
                const roomId = request?.roomId;
                const battleId = request?.battleId;

                global.logUtils.createTraceId(socketUtils.getToken(socket));
                const log = global.logUtils.createContext('SocketListen', api, {userId, roomId, battleId});
                log.debug(`请求参数: `, request);

                if (!API_AUTH_WHITE_NAME_LIST.some(whiteApi => api === whiteApi)) {
                    const token = socketUtils.getToken(socket);
                    const ip = socketUtils.getClientIp(socket);
                    // 会话过期检查
                    const tokenIsExpired = await serviceImpl.checkSessionExpired(userId, token, ip);
                    // log.info(`[${userId}]关联的token[${token}]会话是否过期：[${tokenIsExpired}]`);
                    if (tokenIsExpired) {
                        socket.emit('sessionRecoverRespApi', CryptorUtils.encrypt({
                            code: 'S000003',
                            msg: '您的会话已经过期，请重新登录',
                        }));
                        log.warn(`[${userId}]关联的token[${token}]会话已过期`);
                        // 主动断开
                        socket.disconnect();
                    }
                }
            });

            // 连接服务器时，进行会话检测及恢复（适用于用户未离线，但切后台导致断线的信息）
            this.connectSessionRecover(socket);
            // 用户登录
            this.loginListen(socket);
            // 获取图片验证码
            this.getCaptchaListen(socket);
            // 用户详情查询
            this.userDetailListen(socket);
            // 修改用户详情
            this.modifyUserDetail(socket);
            // 自动生成游客账号
            this.autoGenerateTouristListen(socket);
            // 版本检测
            this.getVersionListen(socket);
            // 获取版本详情检查
            this.getVersionDetailListen(socket);
            // 用户注册
            this.registerListen(socket);
            // 发送验证码(注册验证)
            this.sendValidCodeListen(socket);
            // 找回密码
            this.forgetPasswordListen(socket);
            // 发送验证码(找回密码验证)
            this.forgetPasswordValidCodeListen(socket);
            // 退出登录
            this.loginOutListen(socket);
            // 获取可以观战的房间列表
            this.watchListListen(socket);
            // 加入观战
            this.joinWatchListen(socket);
            // 离开观战
            this.leaveWatchListen(socket);
            // 用户加入房间
            this.joinRoomListen(socket);
            // 用户已准备监听
            this.userReadyListen(socket);
            // 用户离开房间
            this.leaveRoomListen(socket);
            // 用户发起『求和』申请
            this.sendPeaceListen(socket);
            // 对手方对『求和』意愿进行处理
            this.sendPeaceResultListen(socket);
            // 用户发起『悔棋』申请
            this.backMoveListen(socket);
            // 对手方对『悔棋』意愿进行了回应
            this.backMoveResultListen(socket);
            // 对局-棋子传递
            this.moveChessListen(socket);
            // 同步房间信息
            this.syncRoomDataListen(socket);
            // 同步对战信息
            this.syncBattleDataListen(socket);
            // 认输监听
            this.userAdmitDefeatListen(socket);
            // 聊天消息监听和转发
            this.userChatListen(socket);
            // 用户断开连接监听
            this.disconnectListen(socket);
            // 可复盘的对局列表监听
            this.battleReviewListListen(socket);
            // 离开复盘列表
            this.leaveReviewListListen(socket);
            // 复盘某局的数据加载监听
            this.battleReviewDetailListen(socket);
            // 获取分享链接
            this.getShareLinkListen(socket);
            // 在线用户数据统计
            this.onlineCountListen(socket);
            // 房间的观战用户数据统计
            this.watchCountListen(socket);
            // 桌子列表监听
            this.roomListListen(socket);
            // 上传base64图片
            this.uploadBase64(socket);
            // 将用户踢出房间
            this.kickUserListen(socket);
        });
    }

    /**
     * 可复盘的对局列表监听
     * @param socket
     */
    battleReviewListListen = (socket: any) => {
        socket.on('battleReviewApi', async (request: any, fn: Function) => {
            const {userId, pageNum, pageSize} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!pageSize || !pageNum) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '分页参数为空'}));
            } else {
                await serviceImpl.battleReviewImpl(socket, userId, pageNum, pageSize, fn);
            }
        });
    };

    /**
     * 离开复盘列表
     * @param socket
     */
    leaveReviewListListen = (socket: any) => {
        socket.on('leaveReviewListApi', async (request: any, fn: Function) => {
            const {userId} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else {
                await serviceImpl.leaveReviewListImpl(socket, userId, fn);
            }
        });
    };

    /**
     * 加载某局要复盘的数据监听
     * @param socket
     */
    battleReviewDetailListen = (socket: any) => {
        socket.on('battleReviewDetailApi', async (request: any, fn: Function) => {
            const {userId, battleId, pageSize} = CryptorUtils.decrypt(request);
            if (!battleId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '对战号为空'}));
            } else if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!pageSize) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '分页参数为空'}));
            } else {
                await serviceImpl.battleReviewDetailImpl(socket, userId, battleId, pageSize, fn);
            }
        });
    };

    /**
     * 获取分享链接
     * @param socket
     */
    getShareLinkListen = (socket: any) => {
        socket.on('getShareLinkApi', async (request: any, fn: Function) => {
            const {userId, battleId} = CryptorUtils.decrypt(request);
            if (!battleId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '对战号为空'}));
            } else if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else {
                await serviceImpl.getShareLinkImpl(socket, userId, battleId, fn);
            }
        });
    };


    /**
     * 恢复会话数据（客户端一连接就会检测）
     * @param socket
     */
    connectSessionRecover = (socket: any) => {
        setTimeout(async () => {
            const log = global.logUtils.createContext('SocketListen', 'connectSessionRecover');
            const token = socketUtils.getToken(socket);

            // 查询token对应的信息
            log.info(`开始为[${token}]查询会话断连信息`)
            const userStateRows: any = await UserStateDao.queryUserStateTable([['token', token]]);
            log.info(`token: [${token}] 对应的游离信息为：`, userStateRows);

            if (userStateRows && userStateRows.length > 0) {
                const {userId, roomId, battleId} = userStateRows[0];
                log.info(`开始进行会话恢复处理...`);
                await serviceImpl.recoverDetail(socket, userId, roomId, battleId);
            }
        }, 0);
    };

    /**
     * 用户断开连接监听
     * @param socket
     */
    disconnectListen = (socket: any) => {
        socket.on('disconnect', async (reason: string) => {
            global.logUtils.createTraceId(socketUtils.getToken(socket));
            await serviceImpl.disconnectImpl(socket, reason);
        });
    };

    /**
     * 消息转发
     * @param socket
     */
    userChatListen = (socket: any) => {
        socket.on('chatApi', async (request: any, fn: Function) => {
            const {userId, roomId, content} = CryptorUtils.decrypt(request);
            await serviceImpl.userChatImpl(socket, userId, roomId, content, fn);
        });
    };


    /**
     * 对方认输处理
     * @param socket
     */
    userAdmitDefeatListen = (socket: any) => {
        socket.on('userAdmitDefeatApi', async (request: any, fn: Function) => {
            const {userId, battleId} = CryptorUtils.decrypt(request);
            if (!battleId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '对战号为空'}));
            } else if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else {
                lock.acquire('handleGameResultLock', (down: Function) => {
                    serviceImpl.userAdmitDefeatImpl(socket, userId, fn)
                        .then(() => {down()})
                        .catch(() => {down()})
                }, '');
            }
        });
    };

    /**
     * 同步对战数据监听
     * @param socket
     */
    syncBattleDataListen = (socket: any) => {
        socket.on('syncBattleDataApi', async (request: any, fn: Function) => {
            const {battleId, roomId, userId} = CryptorUtils.decrypt(request);
            const log = global.logUtils.createContext('SocketListen', 'syncBattleDataListen', {userId, battleId, roomId});

            if (!battleId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '对战号为空'}));
            } else if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!roomId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '房间号为空'}));
            } else {
                await serviceImpl.syncBattleDataImpl(socket, battleId, userId, roomId, fn);
            }
        });
    };

    /**
     * 同步房间数据监听（非对战数据）
     * @param socket
     */
    syncRoomDataListen = (socket: any) => {
        socket.on('syncRoomDataApi', async (request: any, fn: Function) => {
            const {roomId, userId} = CryptorUtils.decrypt(request);
            const log = global.logUtils.createContext('SocketListen', 'syncRoomDataListen', {userId, roomId});
            
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!roomId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '房间号为空'}));
            } else {
                await serviceImpl.syncRoomDataImpl(socket, userId, roomId, fn);
            }
        });
    };

    /**
     * 对局-移动棋子
     * @param socket
     */
    moveChessListen = (socket: any) => {
        socket.on('moveChessApi', async (request: any, fn: Function) => {
            const {to, chessBox, battleId, roomId, userId} = CryptorUtils.decrypt(request);
            const log = global.logUtils.createContext('SocketListen', 'moveChessListen', {userId, battleId, roomId});

            if (!battleId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '对战号为空'}));
            } else if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!roomId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '房间号为空'}));
            } else if (!to || !chessBox) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '棋子信息为空'}));
            } else {
                // 移动棋子
                lock.acquire('moveChessLock', (down: Function) => {
                    serviceImpl.moveChessImpl(socket, battleId, userId, roomId, to, chessBox, fn)
                        .then((resp: any) => {
                            // 内部错误等情况时，此response不会返回值
                            if(resp) {
                                const { isOver, type, nextIsRedMove } = resp || {};
                                const jobInstance = insUtils.getIns(INS_KEY.JOB_KEY);
                                // 棋子操作事件传递
                                socket.to(roomId).emit('moveChessRespApi', CryptorUtils.encrypt({
                                    code: 'success',
                                    msg: '棋子传递',
                                    // chessFromUserId：棋子来源于哪个用户
                                    data: { to, chessFromUserId: userId},
                                }));
                                // 更新对战的落子方
                                jobInstance.updateBattleData(battleId, {isRedMove: nextIsRedMove});

                                // 我方需要重置超时时间(放在最后，事务提交前)
                                jobInstance.resetBattleStepTime(battleId, userId);

                                // 倒计时数据传递
                                socket.emit('userTimeRespApi', CryptorUtils.encrypt({
                                    code: 'success',
                                    data: {
                                        userList: jobInstance.getBattleTime(battleId),
                                    },
                                }));

                                // 结算游戏(异步结算)
                                isOver && serviceImpl.gameWinImpl(socket, userId, type);
                                // 结果通知回调
                                fn(CryptorUtils.encrypt({code: 'success', msg: '传送成功'}));
                            }
                            down();
                        })
                        .catch((err: Error) => {
                            log.error("移动棋子失败，失败原因：", err);
                            fn(CryptorUtils.encrypt({code: 'fail', msg: err.message || '操作失败'}));
                            down(err);
                        });
                }, (err: any, result: any) => {
                    // release lock
                });
            }
        });
    };

    /**
     * 对方对『请求悔棋』意愿进行了处理
     * @param socket
     */
    backMoveResultListen = (socket: any) => {
        socket.on('backMoveResultApi', async (request: any, fn: Function) => {
            const {userId, roomId, result, battleId} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!roomId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '房间号为空'}));
            } else if (!result) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '悔棋结果为空'}));
            } else if (!battleId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '对战号为空'}));
            } else {
                await serviceImpl.backMoveResultImpl(socket, userId, roomId, result, battleId, fn);
            }
        });
    };

    /**
     * 请求悔棋
     * @param socket
     */
    backMoveListen = (socket: any) => {
        socket.on('backMoveApi', async (request: any, fn: Function) => {
            const {userId, roomId, battleId} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!roomId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '房间号为空'}));
            } else if (!battleId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '对战号为空'}));
            } else if ((await battleUtils.checkWaitingRequest(userId))) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '请等待对方响应'}));
            } else if (!(await battleUtils.checkBackMoveStatus(userId, battleId, roomId))) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '达到最大悔棋次数'}));
            } else {
                await serviceImpl.backMoveImpl(socket, userId, roomId, battleId, fn);
            }
        });
    };

    /**
     * 『求和』对手方处理了求和意愿
     * @param socket
     */
    sendPeaceResultListen = (socket: any) => {
        socket.on('sendPeaceResultApi', async (request: any, fn: Function) => {
            const {userId, roomId, result, battleId} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!roomId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '房间号为空'}));
            } else if (!battleId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '对战号为空'}));
            } else if (!result) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '求和结果为空'}));
            } else {
                // 有结算操作，所以需要加锁
                lock.acquire('sendPeaceResultLock', (down: Function) => {
                    serviceImpl.sendPeaceResultImpl(socket, userId, roomId, result, battleId, fn)
                        .then(() => {down()})
                        .catch(() => {down()});
                }, '');
            }
        });
    };

    /**
     * 发起『求和』
     * @param socket
     */
    sendPeaceListen = (socket: any) => {
        socket.on('sendPeaceApi', async (request: any, fn: Function) => {
            const {userId, roomId, battleId} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!roomId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '房间号为空'}));
            } else if (!battleId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '对战号为空'}));
            } else if ((await battleUtils.checkWaitingRequest(userId))) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '请等待对方响应'}));
            } else if ((await battleUtils.checkShortTimeRepeatSend(userId, BATTLE.REQUEST_INTERVAL_SECONDS, BATTLE_FLOW_TYPE.PEACE))) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: `请距上次${BATTLE.REQUEST_INTERVAL_SECONDS / 60}分钟后再试`}));
            } else {
                await serviceImpl.sendPeaceImpl(socket, userId, roomId, battleId, fn);
            }
        });
    };

    /**
     * 离开房间
     * @param socket
     */
    leaveRoomListen = (socket: any) => {
        socket.on('leaveRoomApi', async (request: any, fn: Function) => {
            const {userId, roomId} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!roomId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '房间号为空'}));
            } else {
                await serviceImpl.leaveRoomImpl(socket, userId, roomId, fn);
            }
        });
    };

    /**
     * 玩家准备
     * @param socket
     */
    userReadyListen = (socket: any) => {
        socket.on('userReadyApi', async (request: any, fn: Function) => {
            const {userId, roomId} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!roomId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '房间号为空'}));
            } else {
                await serviceImpl.userReadyImpl(socket, userId, roomId, fn);
            }
        });
    };

    /**
     * 加入房间监听
     * @param socket
     */
    joinRoomListen = (socket: any) => {
        socket.on('joinRoomApi', async (request: any, fn: Function) => {
            const {userId, joinType, historyRoomIds, appointRoomId} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!joinType) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '房间类型为空'}));
            }/* else if(!(await UserDao.checkUserTypeIsIdentical(userId, appointRoomId))) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '游客无法与正式用户匹配'}));
            } */ else {
                await serviceImpl.joinRoomImpl(socket, userId, joinType, historyRoomIds, appointRoomId, fn);
            }
        });
    };

    /**
     * 离开观战房间监听
     * @param socket
     */
    leaveWatchListen = (socket: any) => {
        socket.on('leaveWatchRoomApi', async (request: any, fn: Function) => {
            const {userId, roomId} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!roomId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '房间号为空'}));
            } else {
                await serviceImpl.leaveWatchImpl(socket, userId, roomId, fn);
            }
        });
    };

    /**
     * 加入观战
     * @param socket
     */
    joinWatchListen = (socket: any) => {
        socket.on('joinWatchApi', async (request: any, fn: Function) => {
            const {userId, roomId} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!roomId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '房间号为空'}));
            } else {
                await serviceImpl.joinWatchImpl(socket, userId, roomId, fn);
            }
        });
    };

    /**
     * 获取可以观战的房间列表
     * @param socket
     */
    watchListListen = (socket: any) => {
        socket.on('watchListApi', async (request: any, fn: Function) => {
            const {userId, pageNum, pageSize} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!pageNum || !pageSize) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: 'pageNum/pageSize为空'}));
            } else {
                await serviceImpl.watchListImpl(socket, userId, pageNum, pageSize, fn);
            }
        });
    };

    /**
     * 退出登录
     * @param socket
     */
    loginOutListen = (socket: any) => {
        socket.on('loginOutApi', async (request: any, fn: Function) => {
            const {userId} = CryptorUtils.decrypt(request);
            if (userId) {
                await serviceImpl.loginOutImpl(socket, userId, fn);
            }
            fn(CryptorUtils.encrypt({code: 'success', msg: '退出成功'}));
        });
    };

    /**
     * 发送验证码监听
     * @param socket
     */
    sendValidCodeListen = (socket: any) => {
        socket.on('sendValidCodeApi', async (request: any, fn: Function) => {
            const {userId, email, userName} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!email) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '邮箱为空'}));
            } else {
                await serviceImpl.sendValidCodeImpl(socket, userId, userName, email, fn);
            }
        });
    };

    /**
     * 找回密码
     * @param socket
     */
    forgetPasswordListen = (socket: any) => {
        socket.on('forgetPasswordApi', async (request: any, fn: Function) => {
            const {userId, email, password, validCode} = CryptorUtils.decrypt(request);

            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!email) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '邮箱为空'}));
            } else if (!password) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '密码为空'}));
            } else if (!(/[A-Za-z]+/).test(password)) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '密码至少包含一位字母'}));
            } else if (!(/[A-Za-z]+[0-9_]*$/.test(password))) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '密码仅支持字母、数字、下划线'}));
            } else {
                await serviceImpl.forgetPasswordImpl(socket, userId, email, password, validCode, fn);
            }
        });
    }

    /**
     * 发送验证码(找回密码验证)
     * @param socket
     */
    forgetPasswordValidCodeListen = (socket: any) => {
        socket.on('forgetSendValidCodeApi', async (request: any, fn: Function) => {
            const {userId, email} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!email) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '邮箱为空'}));
            } else {
                await serviceImpl.forgetPasswordSendValidCodeImpl(socket, userId, email, fn);
            }
        });
    }

    /**
     * 注册账号监听
     * @param socket
     */
    registerListen = (socket: any) => {
        socket.on('registerApi', async (request: any, fn: Function) => {
            const {userId, password, userName, email, validCode, captcha} = CryptorUtils.decrypt(request);
            const ip = socketUtils.getClientIp(socket);
            const finger = socketUtils.getFinger(socket);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!(/^[0-9A-Za-z_]+$/ig.test(userId))) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号仅支持字母数字下划线'}));
            } else if (!userName) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '昵称为空'}));
            } else if (!password) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '密码为空'}));
            } else if (!(/[A-Za-z]+/).test(password)) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '密码至少包含一位字母'}));
            } else if (!(/[A-Za-z]+[0-9_]*$/.test(password))) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '密码仅支持字母、数字、下划线'}));
            } else if (email && !validCode) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '邮箱验证码为空'}));
            } else if (validCode && !email) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '邮箱为空'}));
            } else if (!email && !captcha) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '验证码为空'}));
            } else if (!email && captcha.toString().toLowerCase() !== socket.captcha) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '验证码错误'}));
            } else {
                await serviceImpl.registerImpl(socket, userId, userName, password, email, validCode, fn);
            }
        });
    }

    /**
     * 登录请求监听
     * @param socket
     */
    loginListen = (socket: any) => {
        socket.on('loginApi', async (request: any, fn: Function) => {
            const {userId, password, type, ticket} = CryptorUtils.decrypt(request);
            const log = global.logUtils.createContext('SocketListen', 'loginListen', {userId});

            if (!type) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '登录类型错误'}));
                return;
            } else if (type === LOGIN_TYPE.TICKET && !ticket) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '凭证验证失败'}));
                return;
            } else if (type === LOGIN_TYPE.USER_PASS) {
                if (!userId) {
                    fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
                    return;
                } else if (!password) {
                    fn(CryptorUtils.encrypt({code: 'fail', msg: '密码为空'}));
                    return;
                }
            }
            lock.acquire('loginLock', (down: Function) => {
                const kickUserBy = (token: string) => {
                    const allSockets: any = socketUtils.getSocketsBy(token);
                    log.info(`登录冲突待校验的socket数量：${allSockets.length}, 当前要校验的token: [${token}]`);
                    if(allSockets.length) {
                        const [closeSocket] = allSockets;
                        closeSocket.emit('userConflictRespApi', CryptorUtils.encrypt({
                            code: 'success',
                            msg: '账号登录冲突',
                        }));
                        closeSocket.disconnect();
                        log.info(`[${userId}]使用的旧token[${token}]已被踢下线，新token为：${socketUtils.getToken(socket)}`);
                    }
                }

                // 未登录前，查询该用户的游离数据，将通过旧的token，将其余用户踢出去
                serviceImpl.loginImpl(socket, userId, password, ticket, type, fn)
                    // 登录实现
                    .then(resp => {
                        // 登录成功后
                        const { status, kickToken } = resp || {};
                        if(status) {
                            // 踢出其它账户信息
                            kickUserBy(kickToken);
                            // 会话数据恢复
                            serviceImpl.loginAfterSessionRecover(socket, userId);
                        }
                        down();
                    })
                    .catch(err => {
                        log.error("登录发生错误：", err);
                        down();
                    })
            }, '');
        });
    };

    /**
     * 获取验证码监听
     * @param socket
     */
    getCaptchaListen = (socket: any) => {
        socket.on('getCaptchaApi', async (request: any, fn: Function) => {
            const {svg, code} = CaptchaUtils.getCaptcha();
            fn(CryptorUtils.encrypt({
                code: 'success',
                msg: '获取验证码成功',
                data: {
                    svg: svg
                }
            }));
            // 将验证码挂在socket上面
            socket.captcha = code;
        });
    }

    /**
     * 获取用户详情
     * @param socket
     */
    userDetailListen = (socket: any) => {
        socket.on('userDetailApi', async (request: any, fn: Function) => {
            const {userId} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else {
                await serviceImpl.userDetailImpl(socket, userId, fn);
            }
        });
    };

    /**
     * 修改用户详情
     * @param socket
     */
    modifyUserDetail = (socket: any) => {
        socket.on('modifyUserDetailApi', async (request: any, fn: Function) => {
            // modifyDetail为对象，包含字段和值，{age: 10, name: '小天'}
            const {userId, modifyDetail} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!modifyDetail) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '修改内容为空'}));
            } else {
                await serviceImpl.modifyUserDetailImpl(socket, userId, modifyDetail, fn);
            }
        });
    };

    /**
     * 生成游客账号
     * @param socket
     */
    autoGenerateTouristListen = (socket: any) => {
        socket.on('generateTouristApi', async (request: any, fn: Function) => {
            const {captcha} = CryptorUtils.decrypt(request);
            const token = socketUtils.getToken(socket);
            const ipAddress = socketUtils.getClientIp(socket);
            const finger = socketUtils.getFinger(socket);
            if (!token) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '无法获取验证令牌'}));
            } else if (!captcha) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '验证码为空'}));
            } else if (captcha.toString().toLowerCase() !== socket.captcha) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '验证码错误'}));
            } else {
                await serviceImpl.autoGenerateTouristImpl(socket, token, finger, fn);
            }
        });
    }

    /**
     * 客户端版本检查
     * @param socket
     */
    getVersionListen = (socket: any) => {
        const log = global.logUtils.createContext('SocketListen', 'getVersionListen');
        serviceImpl.getVersionImpl(socket).then(() => {
            log.info('版本检查完成');
        });
    };

    /**
     * 获取版本详情
     * @param socket
     */
    getVersionDetailListen = (socket: any) => {
        const log = global.logUtils.createContext('SocketListen', 'getVersionDetailListen');
        socket.on('versionDetailApi', async (request: any, fn: Function) => {
            log.info('查询版本详细信息');
            await serviceImpl.getVersionDetailImpl(socket, fn);
        });
    };

    /**
     * 在线用户数据统计
     * @param socket
     */
    onlineCountListen = (socket: any) => {
        socket.on('onlineCountApi', async (request: any, fn: Function) => {
            const {userId} = CryptorUtils.decrypt(request);
            const log = global.logUtils.createContext('SocketListen', 'onlineCountListen', {userId});
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else {
                await serviceImpl.onlineCountImpl(socket, userId, fn);
            }
        });
    }

    /**
     * 房间的观战人数信息统计
     * @param socket
     */
    watchCountListen = (socket: any) => {
        socket.on('watchCountApi', async (request: any, fn: Function) => {
            const {userId, roomId} = CryptorUtils.decrypt(request);
            const log = global.logUtils.createContext('SocketListen', 'watchCountListen', {userId});
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!roomId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '房间号为空'}));
            } else {
                await serviceImpl.watchCountImpl(socket, userId, roomId, fn);
            }
        });
    }

    /**
     * 桌子列表监听
     * @param socket
     */
    roomListListen = (socket: any) => {
        socket.on('roomListApi', async (request: any, fn: Function) => {
            const {userId, bindRoomLocation, pageNum, pageSize} = CryptorUtils.decrypt(request);
            const log = global.logUtils.createContext('SocketListen', 'roomListListen', {userId});
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!pageNum) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '页码为空'}));
            } else if (!pageSize) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '每页条数据为空'}));
            } else if (typeof bindRoomLocation === 'undefined') {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '是否绑定房间为空'}));
            } else {
                await serviceImpl.roomListImpl(socket, userId, bindRoomLocation, pageNum, pageSize, fn);
            }
        });
    }

    /**
     * 上传base64
     * @param socket
     */
    uploadBase64 = (socket: any) => {
        socket.on('uploadPictureApi', async (request: any, fn: Function) => {
            const {userId, fileName, fileSize, base64, contentType} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!fileName) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '文件名为空'}));
            } else if (!fileSize) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '文件大小为空'}));
            } else if (!base64) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: 'base64为空'}));
            } else if (!contentType) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '文件类型为空'}));
            } else {
                await serviceImpl.uploadBase64Impl(socket, userId, fileName, fileSize, base64, contentType, fn);
            }
        });
    }

    /**
     * 请求踢人
     * @param socket
     */
    kickUserListen = (socket: any) => {
        socket.on('kickUserApi', async (request: any, fn: Function) => {
            // userId是被踢出的用户
            const {userId, kickUserId, roomId} = CryptorUtils.decrypt(request);
            if (!userId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '账号为空'}));
            } else if (!kickUserId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '被踢人账号为空'}));
            } else if (!roomId) {
                fn(CryptorUtils.encrypt({code: 'fail', msg: '房间号为空'}));
            } else {
                await serviceImpl.kickUserImpl(socket, userId, kickUserId, roomId, fn);
            }
        });
    };
}

export default SocketListen;